﻿/*
* 创建者：LIYINFAI
* 创建时间：2023/10/24 9:35:26
* 描述：
* --------------------------------------
* 修改说明：
* --------------------------------------
*/
using HslCommunication.Core;
using HslCommunication.ModBus;
using HslCommunication;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace CommonLibrary
{
    /// <summary>
    /// 数据通知代理
    /// </summary>
    /// <param name="datas"></param>
    public delegate void ModbusNotifyDelegate(Dictionary<string, object> datas);
    /// <summary>
    /// Modbus帮助类
    /// </summary>
    public class ModbusHelper
    {
        /// <summary>
        /// 配置文件
        /// </summary>
        static string MODBUS_CONFIG_PATH = Path.Combine(CommonHelper.COMMON_CONFIG_DIR, "ModbusConfig.json");
        /// <summary>
        /// 获取配置文件内容
        /// </summary>
        /// <returns></returns>
        public static List<ModbusEntity> GetModbusConfig()
        {
            return CommonHelper.GetJsonListFromFile<ModbusEntity>(MODBUS_CONFIG_PATH);
        }
        /// <summary>
        /// 保存配置文件内容
        /// </summary>
        /// <param name="list"></param>
        public static void SaveModbusConfig(List<ModbusEntity> list)
        {
            CommonHelper.SaveToJsonFile(list, MODBUS_CONFIG_PATH);
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="station"></param>
        /// <param name="dataFormat"></param>
        /// <param name="isStringReserve"></param>
        /// <param name="positionEntities"></param>
        /// <param name="timeout"></param>
        private ModbusHelper(string name, byte station = 0x01,
            EDataFormat dataFormat = EDataFormat.CDAB,
            bool isStringReserve = false,
             List<ModbusPositionEntity> positionEntities = null,
            int timeout = 300)
        {
            this.Name = name;
            this.Station = station;
            this.DataFormat = dataFormat;
            this.IsStringReserve = isStringReserve;
            this.DefaultAddress = positionEntities;
            this.Timeout = timeout;
        }
        /// <summary>
        /// 自检
        /// </summary>
        /// <param name="needSelfCheck"></param>
        private void SelfStart(bool needSelfCheck = true)
        {
            this.CheckConnected();
            if (needSelfCheck)
            {
                new Thread(() =>
                {
                    while (this.mIsSelfCheck)
                    {
                        try
                        {
                            this.CheckConnected();
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Error(ex);
                        }
                        Thread.Sleep(100);
                    }
                }).Start();
            }
        }
        /// <summary>
        /// 创建TCP对象
        /// </summary>
        /// <param name="name"></param>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <param name="station"></param>
        /// <param name="dataFormat"></param>
        /// <param name="isStringReserve"></param>
        /// <param name="needSelfCheck"></param>
        /// <param name="positionEntities"></param>
        /// <returns></returns>
        public static ModbusHelper CreateTcpModbus(string name,
            string ip,
            int port = 502, byte station = 0x01,
            EDataFormat dataFormat = EDataFormat.CDAB,
            bool isStringReserve = false, List<ModbusPositionEntity> positionEntities = null,
            bool needSelfCheck = true)
        {
            ModbusHelper helper = new ModbusHelper(name, station,
                dataFormat, isStringReserve, positionEntities,
                BusinessConfig.Instance().ModbusTimeout);
            helper.TcpEntityData = new TcpEntity()
            {
                IP = ip,
                Port = port,
            };
            helper.CommunicationType = ECommunicationType.MODBUS_TCP;
            helper.SelfStart(needSelfCheck);
            return helper;
        }
        /// <summary>
        /// 创建Rtu对象
        /// </summary>
        /// <param name="name"></param>
        /// <param name="portName"></param>
        /// <param name="baudRate"></param>
        /// <param name="dataBit"></param>
        /// <param name="parity"></param>
        /// <param name="stopBit"></param>
        /// <param name="station"></param>
        /// <param name="dataFormat"></param>
        /// <param name="isStringReserve"></param>
        /// <param name="needSelfCheck"></param>
        /// <param name="positionEntities"></param>
        /// <returns></returns>
        public static ModbusHelper CreateRtuModbus(string name, string portName,
            int baudRate = 9600,
            int dataBit = 8,
            int parity = 0,
            int stopBit = 1,
            byte station = 0x01,
            EDataFormat dataFormat = EDataFormat.CDAB,
            bool isStringReserve = false,
             List<ModbusPositionEntity> positionEntities = null,
            bool needSelfCheck = true)
        {
            ModbusHelper helper = new ModbusHelper(name, station,
                dataFormat, isStringReserve, positionEntities,
                BusinessConfig.Instance().ModbusTimeout);
            helper.SerialEntityData = new SerialEntity()
            {
                BaudRate = baudRate,
                DataBit = dataBit,
                ParityBit = parity,
                PortName = portName,
                StopBit = stopBit
            };
            helper.CommunicationType = ECommunicationType.MODBUS_RTU;
            helper.SelfStart(needSelfCheck);
            return helper;
        }
        /// <summary>
        /// 连接检测
        /// </summary>
        private void CheckConnected()
        {
            if (!this.IsConnected)
            {
                if (this.CommunicationType == ECommunicationType.MODBUS_TCP && this.TcpEntityData != null)
                {
                    this.mModbusTcpNet = new ModbusTcpNet(this.TcpEntityData.IP, this.TcpEntityData.Port, this.Station);
                    this.mModbusTcpNet.IsStringReverse = this.IsStringReserve;
                    this.mModbusTcpNet.DataFormat = (HslCommunication.Core.DataFormat)Enum.Parse(typeof(HslCommunication.Core.DataFormat), this.DataFormat.ToString());
                    this.mModbusTcpNet.ConnectTimeOut = this.Timeout;
                    this.mModbusTcpNet.ReceiveTimeOut = this.Timeout;
                    OperateResult operateResult = this.mModbusTcpNet.ConnectServer();
                    this.mIsConnected = operateResult.IsSuccess;
                }
                else if (this.CommunicationType == ECommunicationType.MODBUS_RTU && this.SerialEntityData != null)
                {
                    this.mModbusRtu = new ModbusRtu(this.Station);
                    this.mModbusRtu.Crc16CheckEnable = true;
                    this.mModbusRtu.SerialPortInni(this.SerialEntityData.PortName, this.SerialEntityData.BaudRate, this.SerialEntityData.DataBit,
                        CommonHelper.GetEnumByRealValue(this.SerialEntityData.StopBit, System.IO.Ports.StopBits.One),
                        CommonHelper.GetEnumByRealValue(this.SerialEntityData.ParityBit, System.IO.Ports.Parity.None));
                    this.mModbusRtu.IsStringReverse = this.IsStringReserve;
                    this.mModbusRtu.DataFormat = (HslCommunication.Core.DataFormat)Enum.Parse(typeof(HslCommunication.Core.DataFormat), this.DataFormat.ToString());
                    this.mModbusRtu.ReceiveTimeout = this.Timeout;
                    OperateResult operateResult = this.mModbusRtu.Open();
                    this.mIsConnected = operateResult.IsSuccess;
                }
            }
            else
            {
                var pingStatus = this.CommunicationType == ECommunicationType.MODBUS_TCP ?
                    this.mModbusTcpNet.IpAddressPing() :
                     (this.mModbusRtu.IsOpen() ? System.Net.NetworkInformation.IPStatus.Success :
                       System.Net.NetworkInformation.IPStatus.TimedOut);
                if (pingStatus == System.Net.NetworkInformation.IPStatus.Success)
                {
                    this.mIsConnected = true;
                }
                else
                {
                    this.mIsConnected = false;
                }
            }
        }
        /// <summary>
        /// 是否连接
        /// </summary>
        public bool IsConnected => mIsConnected;
        /// <summary>
        /// 是否连接
        /// </summary>
        private bool mIsConnected = false;
        /// <summary>
        /// 锁
        /// </summary>
        private readonly object OPT_LOCK = new object();
        /// <summary>
        /// TcpEntity
        /// </summary>
        public TcpEntity TcpEntityData { get; set; }
        /// <summary>
        /// SerialEntity
        /// </summary>
        public SerialEntity SerialEntityData { get; set; }
        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 站点，默认1
        /// </summary>
        public byte Station { get; set; } = 0x01;
        /// <summary>
        /// 字符串是否反转，默认否
        /// </summary>
        public bool IsStringReserve { get; set; } = false;
        /// <summary>
        /// 标题，默认是IP:端口
        /// </summary>
        public string Title
        {
            get
            {
                string result = "";
                if (this.TcpEntityData != null)
                {
                    result = $"{this.TcpEntityData.IP}:{this.TcpEntityData.Port}";
                }
                else if (this.SerialEntityData != null)
                {
                    result = $"{this.SerialEntityData.PortName}";
                }
                return result;
            }
        }
        /// <summary>
        /// 通讯超时时间，默认300ms
        /// </summary>
        public int Timeout { get; set; } = 300;
        /// <summary>
        /// 数据格式，默认CDAB
        /// </summary>
        public EDataFormat DataFormat { get; set; } = EDataFormat.CDAB;
        /// <summary>
        /// 是否运行，默认否
        /// </summary>
        private bool mIsRunning = false;
        /// <summary>
        /// 是否自检，默认是
        /// </summary>
        private bool mIsSelfCheck = true;
        /// <summary>
        /// 执行间隔，默认1000ms
        /// </summary>
        public int Interval { get; set; } = 1000;
        /// <summary>
        /// 缺省读取地址，默认0
        /// </summary>
        public List<ModbusPositionEntity> DefaultAddress { get; set; } = new List<ModbusPositionEntity>();
        /// <summary>
        /// 通讯类型
        /// </summary>
        public ECommunicationType CommunicationType { get; set; } = ECommunicationType.MODBUS_TCP;
        /// <summary>
        /// ModbusTcpNet
        /// </summary>
        private ModbusTcpNet mModbusTcpNet = null;
        /// <summary>
        /// ModbusRtu
        /// </summary>
        private ModbusRtu mModbusRtu = null;
        /// <summary>
        /// 读取的数据
        /// </summary>
        public Dictionary<string, object> ModbusDatas { get; set; } = new Dictionary<string, object>();
        /// <summary>
        /// 读取后通知
        /// </summary>
        public ModbusNotifyDelegate ModbusNotify = null;
        /// <summary>
        /// ModbusDatas 的读写锁
        /// </summary>
        private object LOCK_OPT = new object();
        /// <summary>
        /// 获取读到的数据
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, object> GetModbusDatas()
        {
            Dictionary<string, object> data = new Dictionary<string, object>();
            lock (LOCK_OPT)
            {
                if (ModbusDatas != null && ModbusDatas.Keys.Count > 0)
                {
                    foreach (var key in ModbusDatas.Keys)
                    {
                        data.Add(key, ModbusDatas[key]);
                    }
                }
            }
            return data;
        }
        /// <summary>
        /// 启动
        /// </summary>
        public void Start()
        {
            this.mIsRunning = true;
            new Thread(() =>
            {
                while (this.mIsRunning)
                {
                    lock (LOCK_OPT)
                    {
                        this.ModbusDatas.Clear();
                        foreach (var m in this.DefaultAddress)
                        {
                            if (m.Address != -1)
                            {
                                var data = Read(m.Address, m.Length, m.DataType.ToString().ToLower());
                                if (data != null && data.Count > 0)
                                {
                                    this.ModbusDatas.Add(m.Name, m.Convert(data[0]));
                                }
                                else
                                {
                                    this.ModbusDatas.Add(m.Name, m.DefaultValue);
                                }
                            }
                        }
                    }
                    if (this.ModbusNotify != null)
                    {
                        this.ModbusNotify(GetModbusDatas());
                    }
                    Thread.Sleep(this.Interval);
                }
            }).Start();
        }
        /// <summary>
        /// 计算读取长度
        /// </summary>
        /// <param name="count"></param>
        /// <param name="dataType"></param>
        /// <returns></returns>
        private (ushort length, ushort step) GetReadByteLength(ushort count, string dataType = "short")
        {
            ushort length = 1;
            ushort step = 1;
            dataType = dataType.ToLower();
            if ("long,ulong,float,int,uint,double".Contains(dataType))
            {
                length = (ushort)(2 * count);
                step = 4;
            }
            else if ("short,ushort".Contains(dataType))
            {
                step = 2;
                length = count;
            }
            return (length, step);
        }
        /// <summary>
        /// 数据读取
        /// </summary>
        /// <param name="address"></param>
        /// <param name="length"></param>
        /// <param name="dataType"></param>
        /// <returns></returns>
        public List<object> Read(int address, ushort length, string dataType = "short")
        {
            lock (OPT_LOCK)
            {
                List<object> datas = new List<object>();
                try
                {
                    dataType = dataType.ToLower();
                    bool isValid = (this.CommunicationType == ECommunicationType.MODBUS_TCP && this.mModbusTcpNet != null)
                        || (this.CommunicationType == ECommunicationType.MODBUS_RTU && this.mModbusRtu != null);
                    if (isValid)
                    {
                        var temp = GetReadByteLength(length, dataType);
                        var buffer = this.CommunicationType == ECommunicationType.MODBUS_TCP ?
                            this.mModbusTcpNet.Read(address.ToString(), temp.length)
                            : this.mModbusRtu.Read(address.ToString(), temp.length);
                        IByteTransform byteTransform = this.CommunicationType == ECommunicationType.MODBUS_TCP ?
                         (this.mModbusTcpNet == null ? null : this.mModbusTcpNet.ByteTransform) :
                         (this.mModbusRtu == null ? null : this.mModbusRtu.ByteTransform);
                        ushort startIndex = 0;
                        if (buffer != null && buffer.IsSuccess && byteTransform != null)
                        {
                            for (var i = 0; i < length; i++)
                            {
                                switch (dataType)
                                {
                                    case "bool":
                                        datas.Add(byteTransform.TransBool(buffer.Content, startIndex));
                                        break;
                                    case "ushort":
                                        datas.Add(byteTransform.TransUInt16(buffer.Content, startIndex));
                                        break;
                                    case "short":
                                        datas.Add(byteTransform.TransInt16(buffer.Content, startIndex));
                                        break;
                                    case "uint":
                                        datas.Add(byteTransform.TransUInt32(buffer.Content, startIndex));
                                        break;
                                    case "int":
                                        datas.Add(byteTransform.TransInt32(buffer.Content, startIndex));
                                        break;
                                    case "ulong":
                                        datas.Add(byteTransform.TransUInt64(buffer.Content, startIndex));
                                        break;
                                    case "long":
                                        datas.Add(byteTransform.TransInt64(buffer.Content, startIndex));
                                        break;
                                    case "float":
                                        datas.Add(byteTransform.TransSingle(buffer.Content, startIndex));
                                        break;
                                    case "double":
                                        datas.Add(byteTransform.TransDouble(buffer.Content, startIndex));
                                        break;
                                }
                                startIndex += temp.step;
                            }
                        }
                        else if (buffer != null && !buffer.IsSuccess && !string.IsNullOrWhiteSpace(buffer.Message) && this.mIsRunning)
                        {
                            LogHelper.Debug($"【{this.Name}】 地址：{address.ToString("X4")}, 异常信息：{buffer.Message}");
                        }
                        LogHelper.Info($"【{this.Name}】 地址：{address.ToString("X4")}, 读到数据：{string.Join(" ", datas.Select(v => v.ToString()))}");
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.Error(ex);
                }
                return datas;
            }
        }
        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            this.mIsRunning = false;
        }
        /// <summary>
        /// 退出
        /// </summary>
        public void Close()
        {
            try
            {
                this.mIsRunning = false;
                this.mIsSelfCheck = false;
                if (this.IsConnected)
                {
                    if (this.CommunicationType == ECommunicationType.MODBUS_TCP)
                    {
                        this.mModbusTcpNet.ConnectClose();
                        this.mModbusTcpNet = null;
                    }
                    if (this.CommunicationType == ECommunicationType.MODBUS_RTU)
                    {
                        this.mModbusRtu.Close();
                        this.mModbusRtu = null;
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.Error(ex);
            }
        }
    }
}
